# -*- coding: utf-8 -*-


import os, shutil
import simplejson as json
import logging
import time
import zipfile
import gobject

from threading import Timer
from twisted.internet import protocol
from twisted.protocols import basic
from twisted.internet.protocol import ReconnectingClientFactory

import widgets_manager
from utils.common import COMMANDS, random_filename, validate_file_md5_hash, get_file_md5_hash, read_bytes_from_file


ROOT_DIR = os.path.dirname(__file__)
ZIP_DIR = os.path.join(ROOT_DIR, 'downloadedfiles')
WIDGETS_DIR_REL = 'unzipwidgets'
WIDGETS_DIR_ABS = os.path.join(ROOT_DIR, WIDGETS_DIR_REL)
DOWNLOADED_FILES_FOLDER = os.path.join(ROOT_DIR, 'downloadedfiles')


# Terminal Constants
TELEVISION = 1
ANDROID_PHONE = 2
IPHONE = 3

log = logging.getLogger("AlicanteClutter")

def unzipEvent(zip):
    isdir = os.path.isdir
    join = os.path.join
    norm = os.path.normpath
    split = os.path.split

    #configFile = False
    foldername = split(os.path.splitext(zip.filename)[0])[-1]
    
    zip_folder = join( WIDGETS_DIR_ABS, foldername)
    os.makedirs(zip_folder)

    for each in zip.namelist():
        if not each.endswith('/'):
            root, name = split(each)
            directory = norm(join(zip_folder, root))
            if not isdir(directory):
                os.makedirs(directory)
            file(join(directory, name), 'wb').write(zip.read(each))
    
    # Return relative path to have an import ready path
    return join( WIDGETS_DIR_REL, foldername )



class Event:
    def __init__(self, eventID, eventVersion, channel):
        self.eventID = eventID
        self.running = False
        self.channel = channel
        self.eventType = None
        #self.clutterObject = None
        self.versions = {}
        self.currentVersion = None
        self.setVersion(eventVersion)

    def setVersion(self, version, eventPath = None, archivePath = None):
        self.currentVersion = version
        self.versions[version] = {'eventPath':eventPath, 'archivePath':archivePath, 'downloaded': False, 'loaded': False}

    def setloaded(self):
        self.versions[self.currentVersion]['loaded'] = True
    
    def isDownloaded(self):
        return self.versions[self.currentVersion]['downloaded']
    
    def isLoaded(self):
        return self.versions[self.currentVersion]['loaded']
    
    def start(self):
        self.running = True
        
    def stop(self):
        self.running = False
        
    def newVersion(self, version):
        self.setVersion(version)
    
    def isRunning(self):
        return self.running
        
    def setArchivePath(self, path):
        self.versions[self.currentVersion]['archivePath'] = path
        self.versions[self.currentVersion]['downloaded'] = True
        
    def setPath(self, path):
        self.versions[self.currentVersion]['eventPath'] = os.path.join(path, 'event.py')
        
    def getEventPath(self):
        return self.versions[self.currentVersion]['eventPath']
        
    def getArchivePath(self):
        return self.versions[self.currentVersion]['archivePath']        
    
    
    
class EventsProtocol(basic.LineReceiver):
    delimiter = '\n'
    def __init__(self):
        self.listEvents = {} # { id: <Event object>}
        self.missingEvents = set([])
        self.homebox_available_events = set([]) # set([(id, version), (id, version)])
        self.watchedChannel = None
        
        self.cleanUnzipFolder() #To do, merge those two clean functions
        self.cleanZipFolder()
        
        
    def cleanUnzipFolder(self):
        for root, dirs, files in os.walk(WIDGETS_DIR_ABS):
            for f in files:
                if f != "__init__.py":
                    os.unlink(os.path.join(root, f))
            for d in dirs:
                shutil.rmtree(os.path.join(root, d),  ignore_errors=True)
       
    def cleanZipFolder(self):
        for root, dirs, files in os.walk(DOWNLOADED_FILES_FOLDER):
            for f in files:
                os.unlink(os.path.join(root, f))
            for d in dirs:
                shutil.rmtree(os.path.join(root, d),  ignore_errors=True)
            
            
    def connectionMade(self):
        self.factory.videoManager.setHomeboxConnection(self)
        self.buffer = []
        self.file_handler = None
        self.file_data = ()
        log.debug('Connected to the server')
        
    def connectionLost(self, reason):
        self.factory.videoManager.removeHomeboxConnection()
        self.file_handler = None
        self.file_data = ()
        # Todo: When the the connection with the Homebox is lost and made again, images don't seem to come back
        log.debug('Connection to the server has been lost')
        
    
    def setChannel(self, channelID):
        """
        Call by the video_manager. Show already downloaded events and ask for others
        """
        self.watchedChannel = channelID
        for event in self.listEvents.itervalues():
            event.stop()
            
        self.updateEvents()
        commandtemp = {'action' : 'setchannel', 'value' : channelID}
        self.transport.write( '%s\n' % json.dumps(commandtemp) )
    
    def getMissingEvents(self):
        if not self.missingEvents:
            for eventID, eventVersion in self.homebox_available_events:
                if not self.listEvents[eventID].isDownloaded():
                    self.missingEvents.add( (eventID, eventVersion) )

        if self.missingEvents:
            log.debug("Missing %s" % self.missingEvents)
            # We go one by one
            eventID, eventVersion = self.missingEvents.pop()
            commandtemp = {'action' : 'getfile', 'value' : eventID, 'version' : eventVersion}
            self.transport.write( '%s\n' % json.dumps(commandtemp) )
        else:
            log.debug("No missing event, show the events")
            self.updateEvents()
    
    
    def lineReceived(self, line):
        print line
        try:
            data = json.loads(line)
        except ValueError, e:
            log.error("can't decode: %s" % e)
        else:
            try:
                command = data['action'].upper()
            except (KeyError, ValueError), e:
                log.error("Can't decode: %s" % e)
                
                
            if command == 'PLAY':
                gobject.idle_add( self.factory.videoManager.play )
                
            elif command == 'GETINFOS':
                log.debug("Server wants to know infos")
                self.watchedChannel = 1 # To do, remove that
                commandtemp = {
                                'action' : 'setinfos',
                                'terminaltype':TELEVISION,
                                'startedat':self.factory.startedat,
                                'videoport':self.factory.videoManager.rtp_recv_port,
                                'screensize': {'width':1200, 'height':900}
                              }
                if self.watchedChannel:
                    commandtemp['channel'] = self.watchedChannel
                self.transport.write( '%s\n' % json.dumps(commandtemp) )
                
            elif command == 'LISTEVENTS':
                log.debug("Received the available events for this channel")
                
                try:
                    for eventID, eventVersion in data['value']:
                        if self.listEvents.has_key(eventID):
                            if self.listEvents[eventID].currentVersion < eventVersion:
                                # Remove old version from available event
                                self.homebox_available_events.discard( (eventID, self.listEvents[eventID].currentVersion) ) # Discard is similar to remove
                                self.listEvents[eventID].newVersion(eventVersion)
                                self.homebox_available_events.add( (eventID, eventVersion) )
                            elif self.listEvents[eventID].isLoaded():
                                self.listEvents[eventID].start()
                        else:
                            self.listEvents[eventID] = Event(eventID = eventID, eventVersion = eventVersion, channel = int(data['channel']))
                            self.homebox_available_events.add( (eventID, eventVersion) )
                         
                except (KeyError, ValueError), e:
                    log.error("Can't decode: %s" % e)
                else:
                    self.getMissingEvents()
                     
            elif command == 'HIDEEVENT':
                log.debug("Should hide event")             
                try:
                    event_id = data['id']
                except (KeyError, ValueError), e:
                    log.error("Can't decode: %s" % e)
                else:
                    try:
                        self.listEvents[event_id].stop()
                    except (KeyError, ValueError), e:
                        log.error("Couldn't find event: %s" % e)
                    else:
                        self.updateEvents()
                
            elif command == 'FILETRANSFERT':
                try:
                    file_id = data['id']
                    file_hash = data['hash']
                except (KeyError, ValueError), e:
                    log.error("Can't decode: %s" % e)
                else:
                    rand_filename = random_filename(suffix=".zip", directory=DOWNLOADED_FILES_FOLDER)
                    if rand_filename:
                        self.file_data = (rand_filename, file_id, file_hash)
                        self.setRawMode()
                    else:
                        log.error("Can't find random filename... Weird")
                
            elif command == 'ERRORMSG':
                try:
                    log.error("Error from server %s" % data['value'])
                except:
                    pass
                
            else:
                log.error("Unknown command from server: %s" % command) 
        
    def rawDataReceived(self, data):
        filename = self.file_data[0]
        fileid = self.file_data[1]
        filehash = self.file_data[2]
        
        file_path = os.path.join(DOWNLOADED_FILES_FOLDER, filename)
            
        log.debug( 'Receiving file chunk (%d KB)' % (len(data)) )
        
        if not self.file_handler:
            self.file_handler = open(file_path, 'wb')
            
        if data.endswith('\r\n'):
            # Last chunk
            data = data[:-2]
            self.file_handler.write(data)
            self.setLineMode()
            
            self.file_handler.close()
            self.file_handler = None
            
            if validate_file_md5_hash(file_path, filehash):
                log.debug( 'File %s has been successfully transfered and saved' % (filename) )
            else:
                os.unlink(file_path)
                log.debug( 'File %s has been successfully transfered, but deleted due to invalid MD5 hash' % (filename) )
            try:
                self.listEvents[ fileid ].setArchivePath(file_path)
            except AttributeError:
                log.error("self.events was not initialize")
            self.getMissingEvents()
        else:
            self.file_handler.write(data)
    
    
    def updateEvents(self):
        """
        Unzip new event and display them if matching channel
        """
        for event in self.listEvents.itervalues():
            if event.isDownloaded() == True:
                if event.getEventPath() == None:
                    try:
                        zip = zipfile.ZipFile(event.getArchivePath(), 'r')
                    except IOError, e:
                        log.error(e)
                    else:
                        try:
                            folderpath = unzipEvent(zip)
                        except IOError, e:
                            log.error(e)
                            # Should delete event
                        else:
                            event.setPath(folderpath)   
                            try:
                                self.factory.eventsManager.load( event.eventID, event.getEventPath() )
                            except ImportError, e:
                                log.error(e)
                            else:
                                event.setloaded()
                                event.start()
                            
                        zip.close()
                        
                if (event.channel == self.watchedChannel) and event.isRunning():
                    gobject.idle_add( self.factory.eventsManager.startEvent, event.eventID )
                else:
                    gobject.idle_add( self.factory.eventsManager.stopEvent, event.eventID ) 
        
        gobject.idle_add(self.factory.eventsManager.update_available_events)
    


class ClientHomeboxFactory(ReconnectingClientFactory):
    protocol = EventsProtocol
    
    def __init__(self, eventsManager, videoManager):
        self.eventsManager = eventsManager
        self.videoManager = videoManager
        self.startedat = time.time()
        
    def connectionMade(self):
        self.resetDelay()
        
    def connectionLost(self, connector, reason):
        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
        log.debug( 'Connection to the server has been lost (%s)' % reason )
        
    def clientConnectionFailed(self, connector, reason):
        log.debug( 'Connection failed (%s)' % reason )
        ReconnectingClientFactory.clientConnectionLost(self, connector, reason)   

